import { describe, expect, it } from 'vitest';

import type { AxFunction } from '../ai/types.js';

import { AxFunctionProcessor } from './functions.js';

describe('AxFunctionProcessor undefined/null handling', () => {
  it('should convert undefined return value to empty string', async () => {
    const undefinedFunction: AxFunction = {
      name: 'undefinedFunction',
      description: 'A function that returns undefined',
      parameters: {
        type: 'object',
        properties: {
          input: { type: 'string', description: 'Input parameter' },
        },
        required: ['input'],
      },
      func: async () => {
        return undefined;
      },
    };

    const processor = new AxFunctionProcessor([undefinedFunction]);

    const result = await processor.execute({
      id: 'test_id',
      name: 'undefinedFunction',
      args: '{"input": "test"}',
    });

    expect(result).toBe('');
  });

  it('should convert null return value to empty string', async () => {
    const nullFunction: AxFunction = {
      name: 'nullFunction',
      description: 'A function that returns null',
      parameters: {
        type: 'object',
        properties: {
          input: { type: 'string', description: 'Input parameter' },
        },
        required: ['input'],
      },
      func: async () => {
        return null;
      },
    };

    const processor = new AxFunctionProcessor([nullFunction]);

    const result = await processor.execute({
      id: 'test_id',
      name: 'nullFunction',
      args: '{"input": "test"}',
    });

    expect(result).toBe('');
  });

  it('should preserve string return values', async () => {
    const stringFunction: AxFunction = {
      name: 'stringFunction',
      description: 'A function that returns a string',
      parameters: {
        type: 'object',
        properties: {
          input: { type: 'string', description: 'Input parameter' },
        },
        required: ['input'],
      },
      func: async () => {
        return 'test result';
      },
    };

    const processor = new AxFunctionProcessor([stringFunction]);

    const result = await processor.execute({
      id: 'test_id',
      name: 'stringFunction',
      args: '{"input": "test"}',
    });

    expect(result).toBe('test result');
  });

  it('should convert object return values to JSON string', async () => {
    const objectFunction: AxFunction = {
      name: 'objectFunction',
      description: 'A function that returns an object',
      parameters: {
        type: 'object',
        properties: {
          input: { type: 'string', description: 'Input parameter' },
        },
        required: ['input'],
      },
      func: async () => {
        return { success: true, data: 'test' };
      },
    };

    const processor = new AxFunctionProcessor([objectFunction]);

    const result = await processor.execute({
      id: 'test_id',
      name: 'objectFunction',
      args: '{"input": "test"}',
    });

    expect(result).toBe('{\n  "success": true,\n  "data": "test"\n}');
  });

  it('should handle functions with no parameters returning undefined', async () => {
    const noParamsFunction: AxFunction = {
      name: 'noParamsFunction',
      description: 'A function with no parameters that returns undefined',
      func: async () => {
        return undefined;
      },
    };

    const processor = new AxFunctionProcessor([noParamsFunction]);

    const result = await processor.execute({
      id: 'test_id',
      name: 'noParamsFunction',
      args: '',
    });

    expect(result).toBe('');
  });

  it('should handle functions with no parameters returning null', async () => {
    const noParamsFunction: AxFunction = {
      name: 'noParamsFunction',
      description: 'A function with no parameters that returns null',
      func: async () => {
        return null;
      },
    };

    const processor = new AxFunctionProcessor([noParamsFunction]);

    const result = await processor.execute({
      id: 'test_id',
      name: 'noParamsFunction',
      args: '',
    });

    expect(result).toBe('');
  });
});

describe('AxFunctionProcessor functionResultFormatter', () => {
  it('should use custom formatter from options when provided', async () => {
    const fn = {
      name: 'test_function',
      description: 'A test function',
      func: async () => ({ message: 'hello', value: 42 }),
    };

    const processor = new AxFunctionProcessor([fn]);
    const customFormatter = (result: unknown): string => {
      return `CUSTOM: ${JSON.stringify(result)}`;
    };

    const result = await processor.execute(
      { id: '1', name: 'test_function', args: '{}' },
      { functionResultFormatter: customFormatter }
    );

    expect(result).toBe('CUSTOM: {"message":"hello","value":42}');
  });

  it('should fall back to global formatter when no options formatter provided', async () => {
    const fn = {
      name: 'test_function',
      description: 'A test function',
      func: async () => ({ message: 'hello', value: 42 }),
    };

    const processor = new AxFunctionProcessor([fn]);

    // The global formatter should be used (default JSON.stringify with null, 2)
    const result = await processor.execute(
      { id: '1', name: 'test_function', args: '{}' },
      {}
    );

    expect(result).toBe('{\n  "message": "hello",\n  "value": 42\n}');
  });

  it('should handle string results without formatting', async () => {
    const fn = {
      name: 'test_function',
      description: 'A test function',
      func: async () => 'already a string',
    };

    const processor = new AxFunctionProcessor([fn]);
    const customFormatter = (result: unknown): string => {
      return typeof result === 'string' ? result : `FORMATTED: ${result}`;
    };

    const result = await processor.execute(
      { id: '1', name: 'test_function', args: '{}' },
      { functionResultFormatter: customFormatter }
    );

    expect(result).toBe('already a string');
  });
});
